/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: BUSL-1.1
 */

import { assign } from '@ember/polyfills';
import config from 'nomad-ui/config/environment';
import * as topoScenarios from './topo';
import * as sysbatchScenarios from './sysbatch';
import { pickOne } from '../utils';
import faker from 'nomad-ui/mirage/faker';

const withNamespaces = getConfigValue('mirageWithNamespaces', false);
const withTokens = getConfigValue('mirageWithTokens', true);
const withRegions = getConfigValue('mirageWithRegions', false);

export const allScenarios = {
  smallCluster,
  mediumCluster,
  largeCluster,
  massiveCluster,
  allJobTypes,
  allNodeTypes,
  everyFeature,
  emptyCluster,
  variableTestCluster,
  servicesTestCluster,
  policiesTestCluster,
  rolesTestCluster,
  namespacesTestCluster,
  jobsIndexTestCluster,
  ...topoScenarios,
  ...sysbatchScenarios,
};

export const scenario =
  getScenarioQueryParameter() ||
  getConfigValue('mirageScenario', 'emptyCluster');

export default function (server) {
  const activeScenario = allScenarios[scenario];
  if (!activeScenario) {
    throw new Error(
      `Selected Mirage scenario does not exist.\n\n${scenario} not in list: \n\n\t${Object.keys(
        allScenarios
      ).join('\n\t')}`
    );
  }

  // Make sure built-in node pools exist.
  createBuiltInNodePools(server);
  if (withNamespaces) createNamespaces(server);
  if (withTokens) createTokens(server);
  if (withRegions) createRegions(server);

  activeScenario(server);
}

// Scenarios

function jobsIndexTestCluster(server) {
  faker.seed(1);
  server.createList('agent', 1, 'withConsulLink', 'withVaultLink');
  server.createList('node', 1);
  server.create('node-pool');

  const jobsToCreate = 55;
  for (let i = 0; i < jobsToCreate; i++) {
    let groupCount = Math.floor(Math.random() * 2) + 1;
    server.create('job', {
      name: `Job ${i + 1}`,
      resourceSpec: Array(groupCount).fill('M: 256, C: 500'),
      groupAllocCount: Math.floor(Math.random() * 3) + 1,
      modifyIndex: i + 1,
    });
  }
  server.create('job', 'periodic', {
    name: 'Periodic Job',
    modifyIndex: jobsToCreate + 1,
    childrenCount: 3,
  });

  server.create('job', 'parameterized', {
    name: 'Parameterized Job',
    modifyIndex: jobsToCreate + 2,
    childrenCount: 5,
  });
}

function smallCluster(server) {
  faker.seed(1);
  server.create('feature', { name: 'Dynamic Application Sizing' });
  server.create('feature', { name: 'Sentinel Policies' });
  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
  if (withRegions) {
    server.db.agents[0].member.Tags.region = server.db.regions[0].id;
  }
  server.createList('node-pool', 2);
  server.createList('node', 5);
  server.create(
    'node',
    {
      name: 'node-with-meta',
      meta: { foo: 'bar', baz: 'qux' },
    },
    'withMeta'
  );
  server.createList('job', 10, { createRecommendations: true });
  server.create('job', {
    withGroupServices: true,
    withTaskServices: true,
    name: 'Service-haver',
    id: 'service-haver',
    namespaceId: 'default',
  });
  server.create('job', {
    createAllocations: true,
    groupAllocCount: 150,
    shallow: true,
    allocStatusDistribution: {
      running: 0.5,
      failed: 0.05,
      unknown: 0.2,
      lost: 0.1,
      complete: 0.1,
      pending: 0.05,
    },
    name: 'mixed-alloc-job',
    id: 'mixed-alloc-job',
    namespaceId: 'default',
    type: 'service',
    activeDeployment: true,
  });

  // action-having job
  const actionsJob = server.create('job', {
    createAllocations: true,
    resourceSpec: Array(2).fill('M: 257, C: 500'),
    groupAllocCount: 5,
    groupTaskCount: 2,
    shallow: false,
    name: 'actionable-job',
    id: 'actionable-job',
    namespaceId: 'default',
    type: 'service',
    activeDeployment: false,
    noDeployments: true,
    allocStatusDistribution: {
      running: 1,
    },
    noFailedPlacements: true,
    status: 'running',
    withActions: true,
  });

  // A third task group in the Actions job with a single task/alloc
  const actionsGroup = server.create('task-group', {
    jobId: actionsJob.id,
    name: 'actionable-group',
    count: 1,
  });

  // make sure the allocation generated by that group is running
  server.schema.allocations.findBy({ taskGroup: actionsGroup.name }).update({
    clientStatus: 'running',
  });

  // Set its task state to running
  server.schema.allocations
    .all()
    .filter((x) => x.taskGroup === actionsGroup.name)
    .models[0].taskStates.models[0]?.update({
      state: 'running',
    });

  const pausedJobGroups = 2;
  const pausedTasksPerGroup = 5;
  server.create('job', {
    name: 'paused-job',
    id: 'paused-job',
    namespaceId: 'default',
    type: 'service',
    withPausedTasks: true,
    resourceSpec: Array(pausedJobGroups).fill('M: 256, C: 500'),
    groupTaskCount: pausedTasksPerGroup,
    shallow: false,
    allocStatusDistribution: {
      pending: 1,
    },
    noActiveDeployment: true,
  });

  server.create('policy', {
    id: 'client-reader',
    name: 'client-reader',
    description: "Can read nodes and that's about it",
    rulesJSON: {
      Node: {
        Policy: 'read',
      },
    },
    rules: `# Allow node read access`,
  });

  server.create('policy', {
    id: 'client-writer',
    name: 'client-writer',
    description: 'Can write to nodes',
    rulesJSON: {
      Node: {
        Policy: 'write',
      },
    },
    rules: `# Allow node write access`,
  });

  server.create('policy', {
    id: 'job-reader',
    name: 'job-reader',
    description: "Can read jobs and that's about it",
    rulesJSON: {
      namespace: {
        '*': {
          policy: 'read',
        },
      },
    },
    rules: `# Job read access`,
  });

  server.create('policy', {
    id: 'job-writer',
    name: 'job-writer',
    description: 'Can write jobs',
    rulesJSON: {
      Namespaces: [
        {
          Name: '*',
          Policy: '',
          Capabilities: ['submit-job'],
          Variables: null,
        },
      ],
    },
    rules: `# Job write access`,
  });

  server.create('policy', {
    id: 'variable-lister',
    name: 'variable-lister',
    description: 'Can list variables',
    rulesJSON: {
      namespace: {
        '*': {
          variables: {
            path: {
              capabilities: ['list'],
              pathspec: '*',
            },
          },
        },
      },
    },
    rules: `# Variable list access`,
  });

  server.create('role', {
    id: 'operator',
    name: 'operator',
    description: 'Can operate',
    policyIds: ['client-reader', 'client-writer', 'job-reader', 'job-writer'],
  });

  server.create('role', {
    id: 'sysadmin',
    name: 'sysadmin',
    description: 'Can modify nodes',
    policyIds: ['client-reader', 'client-writer'],
  });

  server.create('token', {
    type: 'client',
    name: 'Tiarna Riarthóir',
    id: 'administrator-token',
    roleIds: ['operator', 'sysadmin'],
    policyIds: ['variable-lister'],
  });

  //#region Active Deployment

  const activelyDeployingJobGroups = 2;
  const activelyDeployingTasksPerGroup = 100;

  const activelyDeployingJob = server.create('job', {
    createAllocations: true,
    groupAllocCount: activelyDeployingTasksPerGroup,
    shallow: true,
    resourceSpec: Array(activelyDeployingJobGroups).fill('M: 257, C: 500'),
    noDeployments: true, // manually created below
    activeDeployment: true,
    allocStatusDistribution: {
      running: 0.6,
      failed: 0.05,
      unknown: 0.05,
      lost: 0,
      complete: 0,
      pending: 0.3,
    },
    name: 'actively-deploying-job',
    id: 'actively-deploying-job',
    namespaceId: 'default',
    type: 'service',
  });

  server.create('deployment', false, 'active', {
    jobId: activelyDeployingJob.id,
    groupDesiredTotal: activelyDeployingTasksPerGroup,
    versionNumber: 1,
    status: 'running',
  });
  server.createList('allocation', 25, {
    jobId: activelyDeployingJob.id,
    jobVersion: 0,
    clientStatus: 'running',
  });

  // Manipulate the above job to show a nice distribution of running, canary, etc. allocs
  let activelyDeployingJobAllocs = server.schema.allocations
    .all()
    .filter((a) => a.jobId === activelyDeployingJob.id);
  activelyDeployingJobAllocs.models
    .filter((a) => a.clientStatus === 'running')
    .slice(0, 10)
    .forEach((a) =>
      a.update({ deploymentStatus: { Healthy: false, Canary: true } })
    );
  activelyDeployingJobAllocs.models
    .filter((a) => a.clientStatus === 'running')
    .slice(10, 20)
    .forEach((a) =>
      a.update({ deploymentStatus: { Healthy: true, Canary: true } })
    );
  activelyDeployingJobAllocs.models
    .filter((a) => a.clientStatus === 'running')
    .slice(20, 65)
    .forEach((a) =>
      a.update({ deploymentStatus: { Healthy: true, Canary: false } })
    );
  activelyDeployingJobAllocs.models
    .filter((a) => a.clientStatus === 'pending')
    .slice(0, 10)
    .forEach((a) =>
      a.update({ deploymentStatus: { Healthy: true, Canary: true } })
    );
  activelyDeployingJobAllocs.models
    .filter((a) => a.clientStatus === 'failed')
    .slice(0, 5)
    .forEach((a) =>
      a.update({ deploymentStatus: { Healthy: true, Canary: false } })
    );

  //#endregion Active Deployment

  // #region Version Tags
  const versionTaggedJob = server.create('job', {
    name: 'version-tag-job',
    id: 'version-tag-job',
    namespaceId: 'default',
    noDeployments: true,
  });

  server.create('job-version', {
    job: versionTaggedJob,
    namespace: 'default',
    version: 0,
  });

  server.create('job-version', {
    job: versionTaggedJob,
    namespace: 'default',
    version: 1,
    versionTag: {
      Name: 'burrito',
      Description: 'A delicious version',
    },
  });

  server.create('job-version', {
    job: versionTaggedJob,
    namespace: 'default',
    version: 2,
    versionTag: {
      Name: 'enchilada',
      Description: 'A version with just a hint of spice',
    },
  });

  // #endregion Version Tags

  createRestartableJobs(server);

  server.create('job', {
    name: 'hcl-definition-job',
    id: 'display-hcl',
    namespaceId: 'default',
  });

  server.create('job', {
    name: 'ui-block-job',
    id: 'ui-block-job',
    ui: {
      Links: [
        {
          Label: 'HashiCorp',
          Url: 'https://hashicorp.com',
        },
        {
          Label: 'Nomad',
          Url: 'https://nomadproject.io',
        },
      ],
      Description:
        'A job with a UI-block defined description and links. It has **bold text** and everything!',
    },
  });

  server.createList('allocFile', 5);
  server.create('allocFile', 'dir', { depth: 2 });
  server.createList('csi-plugin', 2);
  server.createList('variable', 3);

  const variableLinkedJob = server.db.jobs[0];
  const variableLinkedGroup = server.db.taskGroups.findBy({
    jobId: variableLinkedJob.id,
  });
  const variableLinkedTask = server.db.tasks.findBy({
    taskGroupId: variableLinkedGroup.id,
  });
  [
    'a/b/c/foo0',
    'a/b/c/bar1',
    'a/b/c/d/e/foo2',
    'a/b/c/d/e/bar3',
    'a/b/c/d/e/f/foo4',
    'a/b/c/d/e/f/g/foo5',
    'a/b/c/x/y/z/foo6',
    'a/b/c/x/y/z/bar7',
    'a/b/c/x/y/z/baz8',
    'w/x/y/foo9',
    'w/x/y/z/foo10',
    'w/x/y/z/bar11',
    'just some arbitrary file',
    'another arbitrary file',
    'another arbitrary file again',
    'nomad/jobs',
  ].forEach((path) => server.create('variable', { id: path }));

  server.create('variable', {
    id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}/${variableLinkedTask.name}`,
    namespace: variableLinkedJob.namespace,
  });

  server.create('variable', {
    id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}`,
    namespace: variableLinkedJob.namespace,
  });

  server.create('variable', {
    id: `nomad/jobs/${variableLinkedJob.id}`,
    namespace: variableLinkedJob.namespace,
  });

  const newJobName = 'new-job';
  const newJobTaskGroupName = 'redis';
  const jsonJob = (overrides) => {
    return JSON.stringify(
      assign(
        {},
        {
          Name: newJobName,
          Namespace: 'default',
          Datacenters: ['dc1'],
          Priority: 50,
          TaskGroups: [
            {
              Name: newJobTaskGroupName,
              Tasks: [
                {
                  Name: 'redis',
                  Driver: 'docker',
                },
              ],
            },
          ],
        },
        overrides
      ),
      null,
      2
    );
  };

  server.create('variable', {
    id: `nomad/job-templates/foo-bar`,
    namespace: 'namespace-2',
    Items: {
      description: 'a description',
      template: jsonJob(),
    },
  });

  server.create('variable', {
    id: `nomad/job-templates/baz-qud`,
    namespace: 'default',
    Items: {
      description: 'another different description',
      template: jsonJob(),
    },
  });

  server.create('variable', {
    id: 'Auto-conflicting Variable',
    namespace: 'default',
  });

  // #region evaluations

  // Branching: a single eval that relates to N-1 mutually-unrelated evals
  const NUM_BRANCHING_EVALUATIONS = 3;
  Array(NUM_BRANCHING_EVALUATIONS)
    .fill()
    .map((_, i) => {
      return {
        evaluation: server.create('evaluation', {
          id: `branching_${i}`,
          previousEval: i > 0 ? `branching_0` : '',
          jobID: pickOne(server.db.jobs).id,
        }),

        evaluationStub: server.create('evaluation-stub', {
          id: `branching_${i}`,
          previousEval: i > 0 ? `branching_0` : '',
          status: 'failed',
        }),
      };
    })
    .map((x, i, all) => {
      x.evaluation.update({
        relatedEvals:
          i === 0
            ? all.filter((_, j) => j !== 0).map((e) => e.evaluation)
            : all.filter((_, j) => j !== i).map((e) => e.evaluation),
      });
      return x;
    });

  // Linear: a long line of N related evaluations
  const NUM_LINEAR_EVALUATIONS = 20;
  Array(NUM_LINEAR_EVALUATIONS)
    .fill()
    .map((_, i) => {
      return {
        evaluation: server.create('evaluation', {
          id: `linear_${i}`,
          previousEval: i > 0 ? `linear_${i - 1}` : '',
          jobID: pickOne(server.db.jobs).id,
        }),

        evaluationStub: server.create('evaluation-stub', {
          id: `linear_${i}`,
          previousEval: i > 0 ? `linear_${i - 1}` : '',
          nextEval: `linear_${i + 1}`,
          status: 'failed',
        }),
      };
    })
    .map((x, i, all) => {
      x.evaluation.update({
        relatedEvals: all.filter((_, j) => i !== j).map((e) => e.evaluation),
      });
      return x;
    });

  // #endregion evaluations

  const csiAllocations = server.createList('allocation', 5);
  const volumes = server.schema.csiVolumes.all().models;
  csiAllocations.forEach((alloc) => {
    const volume = pickOne(volumes);
    volume.writeAllocs.add(alloc);
    volume.readAllocs.add(alloc);
    volume.save();
  });

  server.create('dynamic-host-volume', {
    name: 'dynamic-host-volume',
    namespaceId: 'default',
    createTime: new Date().getTime() * 1000000,
    modifyTime: new Date().getTime() * 1000000,
    allocations: csiAllocations,
  });

  server.create('auth-method', { name: 'vault' });
  server.create('auth-method', { name: 'auth0' });
  server.create('auth-method', { name: 'cognito' });
  server.create('auth-method', { name: 'JWT-Local', type: 'JWT' });
}

function mediumCluster(server) {
  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
  server.createList('node-pool', 5);
  server.createList('node', 50);
  server.createList('job', 25);
}

function variableTestCluster(server) {
  faker.seed(1);
  createTokens(server);
  server.create('token', {
    name: 'Novars Murphy',
    id: 'n0-v4r5-4cc355',
    type: 'client',
  });
  createNamespaces(server);
  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
  server.createList('node-pool', 3);
  server.createList('node', 5);
  server.createList('job', 3);
  server.createList('variable', 3);
  // server.createList('allocFile', 5);
  // server.create('allocFile', 'dir', { depth: 2 });
  // server.createList('csi-plugin', 2);

  const variableLinkedJob = server.db.jobs[0];
  const variableLinkedGroup = server.db.taskGroups.findBy({
    jobId: variableLinkedJob.id,
  });
  const variableLinkedTask = server.db.tasks.findBy({
    taskGroupId: variableLinkedGroup.id,
  });
  [
    'a/b/c/foo0',
    'a/b/c/bar1',
    'a/b/c/d/e/foo2',
    'a/b/c/d/e/bar3',
    'a/b/c/d/e/f/foo4',
    'a/b/c/d/e/f/g/foo5',
    'a/b/c/x/y/z/foo6',
    'a/b/c/x/y/z/bar7',
    'a/b/c/x/y/z/baz8',
    'w/x/y/foo9',
    'w/x/y/z/foo10',
    'w/x/y/z/bar11',
  ].forEach((path) => server.create('variable', { id: path }));

  server.create('variable', {
    id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}/${variableLinkedTask.name}`,
    namespace: variableLinkedJob.namespace,
  });

  server.create('variable', {
    id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}`,
    namespace: variableLinkedJob.namespace,
  });

  server.create('variable', {
    id: `nomad/jobs/${variableLinkedJob.id}`,
    namespace: variableLinkedJob.namespace,
  });

  server.create('variable', {
    id: 'just some arbitrary file',
    namespace: 'namespace-2',
  });

  server.create('variable', {
    id: 'another arbitrary file',
    namespace: 'namespace-2',
  });

  server.create('variable', {
    id: 'another arbitrary file again',
    namespace: 'namespace-2',
  });

  server.create('variable', {
    id: 'Auto-conflicting Variable',
    namespace: 'default',
  });
}

function policiesTestCluster(server, options = { sentinel: false }) {
  if (options.sentinel) {
    server.create('feature', { name: 'Sentinel Policies' });
    server.create('sentinel-policy', {
      id: 'policy-1',
      name: 'policy-1',
      description: 'A sentinel policy generated by Mirage',
      enforcementLevel: 'soft-mandatory',
      policy:
        'import "time"\n\nis_weekday = rule { time.day not in ["friday", "saturday", "sunday"] }\nis_open_hours = rule { time.hour > 8 and time.hour < 16 }\n\nmain = rule { is_open_hours and is_weekday }',
      scope: 'submit-job',
    });

    server.create('sentinel-policy', {
      id: 'host-volume-policy',
      name: 'host-volume-policy',
      description: 'A sentinel policy generated by Mirage',
      enforcementLevel: 'soft-mandatory',
      policy: `
has_tag = func() {
print("volume is missing tag")
tag = volume.parameters["tag"] else 0
return tag is not 0
}
main = rule { has_tag() }
      `,
      scope: 'submit-host-volume',
    });
    server.createList('sentinel-policy', 5);
  }

  faker.seed(1);
  createTokens(server);
  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
}

function rolesTestCluster(server) {
  faker.seed(1);

  server.create('namespace', {
    id: 'default',
    name: 'default',
  });
  server.createList('namespace', 4);
  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
  server.createList('node-pool', 2);
  server.createList('node', 5);
  server.createList('job', 5);

  // createTokens(server);

  // Create policies
  const clientReaderPolicy = server.create('policy', {
    id: 'client-reader',
    name: 'client-reader',
    description: "Can read nodes and that's about it",
    rulesJSON: {
      Node: {
        Policy: 'read',
      },
    },
  });

  const clientWriterPolicy = server.create('policy', {
    id: 'client-writer',
    name: 'client-writer',
    description: 'Can write to nodes',
    rulesJSON: {
      Node: {
        Policy: 'write',
      },
    },
  });

  const clientDenierPolicy = server.create('policy', {
    id: 'client-denier',
    name: 'client-denier',
    description: "Can't do anything with Clients",
    rulesJSON: {
      Node: {
        Policy: 'deny',
      },
    },
  });

  const jobDenierPolicy = server.create('policy', {
    id: 'job-denier',
    name: 'job-denier',
    description: "Can't do anything with Jobs",
    rulesJSON: {
      namespace: {
        '*': {
          policy: 'deny',
        },
      },
    },
  });

  const operatorPolicy = server.create('policy', {
    id: 'operator',
    name: 'operator',
    description: 'Can operate',
    rulesJSON: {
      operator: {
        policy: 'write',
      },
    },
  });

  const jobReaderPolicy = server.create('policy', {
    id: 'job-reader',
    name: 'job-reader',
    description: 'Can learn about jobs',
    rulesJSON: {
      namespace: {
        '*': {
          policy: 'read',
        },
      },
    },
  });

  const highLevelJobPolicy = server.create('policy', {
    id: 'job-writer',
    name: 'job-writer',
    description: 'Can do lots with jobs',
    rulesJSON: {
      Namespaces: [
        {
          Name: '*',
          Policy: '',
          Capabilities: ['submit-job'],
          Variables: null,
        },
      ],
    },
  });

  // Create roles
  const editorRole = server.create('role', {
    id: 'editor',
    name: 'editor',
    description: 'Can edit things',
    policyIds: [clientWriterPolicy.id],
  });

  const highLevelRole = server.create('role', {
    id: 'high-level',
    name: 'high-level',
    description: 'Can do lots of things',
    policyIds: [highLevelJobPolicy.id],
  });

  const readerRole = server.create('role', {
    id: 'reader',
    name: 'reader',
    description: 'Can read things',
    policyIds: [clientReaderPolicy.id, jobReaderPolicy.id],
  });

  const denierRole = server.create('role', {
    id: 'denier',
    name: 'denier',
    description: "Can't do anything",
    policyIds: [clientDenierPolicy.id, jobDenierPolicy.id],
  });

  // Create tokens

  let managementToken = server.create('token', {
    type: 'management',
    name: 'Management Token',
  });

  let clientReaderToken = server.create('token', {
    type: 'client',
    name: "N. O'DeReader",
    policyIds: [clientReaderPolicy.id],
  });

  let clientWriterToken = server.create('token', {
    type: 'client',
    name: "N. O'DeWriter",
    policyIds: [clientWriterPolicy.id],
  });

  let dualPolicyToken = server.create('token', {
    type: 'client',
    name: 'Multi-policy Token',
    policyIds: [clientReaderPolicy.id, clientWriterPolicy.id],
  });

  let highLevelViaPolicyToken = server.create('token', {
    type: 'client',
    name: 'High Level Policy Token',
    policyIds: [highLevelJobPolicy.id],
  });

  let highLevelViaRoleToken = server.create('token', {
    type: 'client',
    name: 'High Level Role Token',
    roleIds: [highLevelRole.id],
  });

  let policyAndRoleToken = server.create('token', {
    type: 'client',
    name: 'Policy And Role Token',
    policyIds: [operatorPolicy.id],
    roleIds: [readerRole.id],
  });

  let multiRoleToken = server.create('token', {
    type: 'client',
    name: 'Multi Role Token',
    roleIds: [editorRole.id, highLevelRole.id],
  });

  let multiRoleAndPolicyToken = server.create('token', {
    type: 'client',
    name: 'Multi Role And Policy Token',
    roleIds: [editorRole.id, highLevelRole.id],
    policyIds: [clientWriterPolicy.id], // also included within editorRole, so redundant here.
  });

  let noClientsViaPolicyToken = server.create('token', {
    type: 'client',
    name: 'Clientless Policy Token',
    policyIds: [clientDenierPolicy.id],
  });

  let noClientsViaRoleToken = server.create('token', {
    type: 'client',
    name: 'Clientless Role Token',
    roleIds: [denierRole.id],
  });

  // malleable test token
  server.create('token', {
    name: 'Clay-Token',
    id: 'cl4y-t0k3n',
    type: 'client',
    policyIds: [clientReaderPolicy.id, operatorPolicy.id],
    roleIds: [editorRole.id],
    expirationTime: new Date(new Date().getTime() + 60 * 60 * 1000),
  });

  logTokens(server);

  server.create('auth-method', { name: 'vault' });

  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
}

function namespacesTestCluster(server, opts = { enterprise: true }) {
  faker.seed(1);
  createTokens(server);

  if (opts.enterprise) {
    server.create('feature', { name: 'Node Pools Governance' });
    server.create('feature', { name: 'Resource Quotas' });
  }

  createNamespaces(server);

  let nsWithVariable = server.create('namespace', {
    name: 'with-variables',
    id: 'with-variables',
  });

  server.create('variable', {
    id: `some/variable/path`,
    namespace: nsWithVariable.id,
  });

  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
}

function servicesTestCluster(server) {
  faker.seed(1);
  server.create('feature', { name: 'Dynamic Application Sizing' });
  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
  server.createList('node-pool', 3);
  server.createList('node', 5);
  server.createList('job', 1, { createRecommendations: true });
  server.create('job', {
    withGroupServices: true,
    withTaskServices: true,
    name: 'Service-haver',
    id: 'service-haver',
    namespaceId: 'default',
  });
  server.createList('allocFile', 5);
  server.create('allocFile', 'dir', { depth: 2 });
  server.createList('csi-plugin', 2);
  server.createList('variable', 3);

  const variableLinkedJob = server.db.jobs[0];
  const variableLinkedGroup = server.db.taskGroups.findBy({
    jobId: variableLinkedJob.id,
  });
  const variableLinkedTask = server.db.tasks.findBy({
    taskGroupId: variableLinkedGroup.id,
  });
  [
    'a/b/c/foo0',
    'a/b/c/bar1',
    'a/b/c/d/e/foo2',
    'a/b/c/d/e/bar3',
    'a/b/c/d/e/f/foo4',
    'a/b/c/d/e/f/g/foo5',
    'a/b/c/x/y/z/foo6',
    'a/b/c/x/y/z/bar7',
    'a/b/c/x/y/z/baz8',
    'w/x/y/foo9',
    'w/x/y/z/foo10',
    'w/x/y/z/bar11',
    'just some arbitrary file',
    'another arbitrary file',
    'another arbitrary file again',
  ].forEach((path) => server.create('variable', { id: path }));

  server.create('variable', {
    id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}/${variableLinkedTask.name}`,
    namespace: variableLinkedJob.namespace,
  });

  server.create('variable', {
    id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}`,
    namespace: variableLinkedJob.namespace,
  });

  server.create('variable', {
    id: `nomad/jobs/${variableLinkedJob.id}`,
    namespace: variableLinkedJob.namespace,
  });

  server.create('variable', {
    id: 'Auto-conflicting Variable',
    namespace: 'default',
  });

  // #region evaluations

  // Branching: a single eval that relates to N-1 mutually-unrelated evals
  const NUM_BRANCHING_EVALUATIONS = 3;
  Array(NUM_BRANCHING_EVALUATIONS)
    .fill()
    .map((_, i) => {
      return {
        evaluation: server.create('evaluation', {
          id: `branching_${i}`,
          previousEval: i > 0 ? `branching_0` : '',
          jobID: pickOne(server.db.jobs).id,
        }),

        evaluationStub: server.create('evaluation-stub', {
          id: `branching_${i}`,
          previousEval: i > 0 ? `branching_0` : '',
          status: 'failed',
        }),
      };
    })
    .map((x, i, all) => {
      x.evaluation.update({
        relatedEvals:
          i === 0
            ? all.filter((_, j) => j !== 0).map((e) => e.evaluation)
            : all.filter((_, j) => j !== i).map((e) => e.evaluation),
      });
      return x;
    });

  // Linear: a long line of N related evaluations
  const NUM_LINEAR_EVALUATIONS = 20;
  Array(NUM_LINEAR_EVALUATIONS)
    .fill()
    .map((_, i) => {
      return {
        evaluation: server.create('evaluation', {
          id: `linear_${i}`,
          previousEval: i > 0 ? `linear_${i - 1}` : '',
          jobID: pickOne(server.db.jobs).id,
        }),

        evaluationStub: server.create('evaluation-stub', {
          id: `linear_${i}`,
          previousEval: i > 0 ? `linear_${i - 1}` : '',
          nextEval: `linear_${i + 1}`,
          status: 'failed',
        }),
      };
    })
    .map((x, i, all) => {
      x.evaluation.update({
        relatedEvals: all.filter((_, j) => i !== j).map((e) => e.evaluation),
      });
      return x;
    });

  // #endregion evaluations

  const csiAllocations = server.createList('allocation', 5);
  const volumes = server.schema.csiVolumes.all().models;
  csiAllocations.forEach((alloc) => {
    const volume = pickOne(volumes);
    volume.writeAllocs.add(alloc);
    volume.readAllocs.add(alloc);
    volume.save();
  });
}

// Due to Mirage performance, large cluster scenarios will be slow
function largeCluster(server) {
  server.createList('agent', 5);
  server.createList('node-pool', 10);
  server.createList('node', 1000);
  server.createList('job', 100);
}

function massiveCluster(server) {
  server.createList('agent', 7);
  server.createList('node-pool', 100);
  server.createList('node', 5000);
  server.createList('job', 2000);
}

function allJobTypes(server) {
  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
  server.createList('node', 5);

  server.create('job', { type: 'service' });
  server.create('job', { type: 'batch' });
  server.create('job', { type: 'system' });
  server.create('job', 'periodic');
  server.create('job', 'parameterized');
  server.create('job', 'periodicSysbatch');
  server.create('job', 'parameterizedSysbatch');
  server.create('job', { failedPlacements: true });
}

function allNodeTypes(server) {
  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');

  server.create('node');
  server.create('node', 'forceIPv4');
  server.create('node', 'draining');
  server.create('node', 'forcedDraining');
  server.create('node', 'noDeadlineDraining');
  server.create('node', 'withMeta');

  server.createList('job', 3);
}

function everyFeature(server) {
  server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
  server.createList('node-pool', 3);

  server.create('node', 'forceIPv4');
  server.create('node', 'draining');
  server.create('node', 'forcedDraining');
  server.create('node', 'noDeadlineDraining');
  server.create('node', 'withMeta');

  const job1 = server.create('job', {
    type: 'service',
    activeDeployment: true,
    namespaceId: 'default',
    createAllocations: false,
  });
  server.create('job', {
    type: 'batch',
    failedPlacements: true,
    namespaceId: 'default',
  });
  server.create('job', { type: 'system', namespaceId: 'default' });
  server.create('job', 'periodic', { namespaceId: 'default' });
  server.create('job', 'parameterized', { namespaceId: 'default' });

  server.create('allocation', 'rescheduled', { jobId: job1.id });
  server.create('allocation', 'preempter', { jobId: job1.id });
  server.create('allocation', 'preempted', { jobId: job1.id });
}

function emptyCluster(server) {
  server.create('agent');
  server.create('node');
}

// Behaviors

function createBuiltInNodePools(server) {
  server.create('node-pool', { name: 'default' });
  server.create('node-pool', { name: 'all' });
}

function createTokens(server) {
  server.createList('token', 3);
  server.create('token', {
    name: 'Secure McVariables',
    id: '53cur3-v4r14bl35',
  });
  server.create('token', {
    name: "Safe O'Constants",
    id: 'f3w3r-53cur3-v4r14bl35',
  });
  server.create('token', {
    name: 'Lazarus MacMarbh',
    id: '3XP1R35-1N-3L3V3N-M1NU735',
  });
  logTokens(server);
}

function createNamespaces(server) {
  server.createList('namespace', 3);
}

function createRegions(server) {
  ['americas', 'europe', 'asia', 'some-long-name-just-to-test'].forEach(
    (id) => {
      server.create('region', { id });
    }
  );
}

/* eslint-disable */
function logTokens(server) {
  console.log('TOKENS:');
  server.db.tokens.forEach((token) => {
    console.log(`
Name: ${token.name}
Secret: ${token.secretId}
Accessor: ${token.accessorId}

`);
  });

  console.log(
    'Alternatively, log in with a JWT. If it ends with `management`, you have full access. If it ends with `bad`, you`ll get an error. Otherwise, you`ll get a token with limited access.'
  );
  console.log('=====================================');
}

function getConfigValue(variableName, defaultValue) {
  const value = config.APP[variableName];
  if (value !== undefined) return value;

  console.warn(
    `No ENV.APP value set for "${variableName}". Defaulting to "${defaultValue}". To set a custom value, modify config/environment.js`
  );
  return defaultValue;
}

function getScenarioQueryParameter() {
  const params = new URLSearchParams(window.location.search);
  const mirageScenario = params.get('mirage-scenario');
  if (mirageScenario && !(mirageScenario in allScenarios)) {
    console.error(
      new Error(
        `Selected Mirage scenario does not exist.\n\n${mirageScenario} not in list: \n\n\t${Object.keys(
          allScenarios
        ).join('\n\t')}`
      )
    );
    return 'smallCluster';
  }
  return mirageScenario;
}
/* eslint-enable */

export function createRestartableJobs(server) {
  const restartableJob = server.create('job', {
    name: 'restartable-job',
    stopped: true,
    status: 'dead',
    noDeployments: true,
    shallow: true,
    createAllocations: false,
    groupAllocCount: 0,
  });

  const revertableJob = server.create('job', {
    name: 'revertable-job',
    stopped: false,
    status: 'dead',
    noDeployments: true,
    shallow: true,
    createAllocations: false,
    groupAllocCount: 0,
    type: 'service',
  });

  const nonRevertableJob = server.create('job', {
    name: 'non-revertable-job',
    stopped: false,
    status: 'dead',
    shallow: true,
    createAllocations: false,
    groupAllocCount: 0,
    type: 'service',
  });

  const revertableBatchJob = server.create('job', {
    name: 'revertable-batch-job',
    stopped: false,
    status: 'dead',
    noDeployments: true,
    shallow: true,
    type: 'batch',
  });

  // So it shows up as "Failed" instead of "Scaled Down"
  restartableJob.taskGroups.models[0].update({
    count: 1,
  });
  revertableJob.taskGroups.models[0].update({
    count: 1,
  });
  nonRevertableJob.taskGroups.models[0].update({
    count: 1,
  });

  // Remove all job-versions inherently created
  server.schema.jobVersions
    .all()
    .filter((v) => v.jobId === restartableJob.id)
    .models.forEach((v) => v.destroy());
  server.schema.jobVersions
    .all()
    .filter((v) => v.jobId === revertableJob.id)
    .models.forEach((v) => v.destroy());
  server.schema.jobVersions
    .all()
    .filter((v) => v.jobId === nonRevertableJob.id)
    .models.forEach((v) => v.destroy());
  server.schema.jobVersions
    .all()
    .filter((v) => v.jobId === revertableBatchJob.id)
    .models.forEach((v) => v.destroy());

  server.create('job-version', {
    job: revertableJob,
    namespace: revertableJob.namespace,
    version: 0,
    stable: false,
    versionTag: {
      Name: 'v0',
      Description: 'The first version',
    },
  });

  server.create('job-version', {
    job: revertableJob,
    namespace: revertableJob.namespace,
    version: 1,
    stable: true,
    versionTag: {
      Name: 'v1',
      Description: 'The second version',
    },
  });

  server.create('job-version', {
    job: revertableJob,
    namespace: revertableJob.namespace,
    version: 2,
    stable: false,
    versionTag: {
      Name: 'v2',
      Description: 'The third version',
    },
  });

  server.create('job-version', {
    job: nonRevertableJob,
    namespace: nonRevertableJob.namespace,
    version: 0,
    stable: false,
    noActiveDeployment: true,
  });

  server.create('job-version', {
    job: nonRevertableJob,
    namespace: nonRevertableJob.namespace,
    version: 1,
    stable: false,
    noActiveDeployment: true,
  });

  server.create('job-version', {
    job: revertableBatchJob,
    namespace: revertableBatchJob.namespace,
    version: 0,
    stable: false, // <--- ignored by the UI by way of job.hasVersionStability
    noActiveDeployment: true,
  });

  server.create('job-version', {
    job: revertableBatchJob,
    namespace: revertableBatchJob.namespace,
    version: 1,
    stable: false, // <--- ignored by the UI by way of job.hasVersionStability
    noActiveDeployment: true,
  });

  server.schema.jobVersions
    .all()
    .filter((v) => v.jobId === revertableJob.id)
    .models.forEach((v) => v.update({ stable: true }));
  server.schema.jobVersions
    .all()
    .filter((v) => v.jobId === nonRevertableJob.id)
    .models.forEach((v) => v.update({ stable: false }));
}
